using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;
using Oracle.ManagedDataAccess.Client;
using OracleInternal.Common;

namespace OracleInternal.NotificationServices
{
	internal class NodeList
	{
		private object publishLock;

		private List<Notification> publishQueue;

		private object connLock;

		private Connection[] connections;

		private bool connCheck;

		private List<ReceiverThread> staleReceivers;

		private int concurrency;

		private int senderIndex;

		private SenderThread senderThread;

		private string id;

		private bool failedOver_Renamed_Field;

		private bool shutdown;

		private Concurrency[] concurrencies;

		private Hashtable notifications;

		private long lastCleanupTime;

		protected internal ONS ons;

		protected internal NodeList failOverList;

		protected internal bool active;

		protected internal long shutdownTimeout;

		private const long ConnectionDelay = 30000L;

		private const char ONS_HP_SEPARATOR = ',';

		protected internal virtual NodeList FailOver
		{
			set
			{
				if (value == null)
				{
					joinStaleRecievers(null);
					for (int i = 0; i < connections.Length; i++)
					{
						connections[i].ClientShutdown = false;
					}
				}
				failOverList = value;
			}
		}

		public virtual string Id => id;

		protected internal virtual long Shutdown
		{
			set
			{
				setConnectionsBusy();
				lock (connLock)
				{
					if (shutdown)
					{
						clearConnectionsBusy();
						return;
					}
					shutdown = true;
					shutdownTimeout = value;
				}
				clearConnectionsBusy();
			}
		}

		public string getId()
		{
			return id;
		}

		public NodeList(string listId, string listNodes, int listConcurrency, bool listActive, ONS listONS)
		{
			ons = listONS;
			id = listId;
			concurrency = listConcurrency;
			active = listActive;
			shutdownTimeout = ons.shutdowntimeout;
			failedOver_Renamed_Field = false;
			shutdown = false;
			buildConnectionArray(listNodes);
			publishLock = new object();
			publishQueue = new List<Notification>();
			connLock = new object();
			staleReceivers = new List<ReceiverThread>();
			notifications = new Hashtable();
			lastCleanupTime = 0L;
			concurrency = connections.Length;
			if (concurrency > listConcurrency)
			{
				concurrency = listConcurrency;
			}
			concurrencies = new Concurrency[concurrency];
			for (int i = 0; i < concurrency; i++)
			{
				concurrencies[i] = new Concurrency(i);
			}
		}

		protected internal virtual void start(bool force)
		{
			lock (connLock)
			{
				if (shutdown || (!active && !force))
				{
					return;
				}
			}
			lock (connLock)
			{
				for (int i = 0; i < concurrency; i++)
				{
					concurrencies[i].assign(connections[i]);
					startConnection(connections[i]);
				}
			}
		}

		private void startConnection(Connection conn)
		{
			lock (connLock)
			{
				if (shutdown)
				{
					return;
				}
			}
			ReceiverThread receiverThread = new ReceiverThread(this, conn);
			SenderThread obj = new SenderThread(this, conn);
			receiverThread.Start();
			obj.Start();
		}

		private SenderThread getSenderThread(bool wakeNew)
		{
			if (senderThread == null)
			{
				int i = 0;
				int num = senderIndex;
				for (; i < connections.Length; i++)
				{
					if (num == connections.Length)
					{
						num = 0;
					}
					if (connections[num].sender != null && connections[num].socket != null)
					{
						senderThread = connections[num].sender;
						if (senderIndex != num)
						{
							senderIndex = num;
						}
						if (wakeNew)
						{
							senderThread.wakeThread();
						}
						break;
					}
					num++;
				}
			}
			return senderThread;
		}

		protected internal virtual Notification getFirstPublished(SenderThread st)
		{
			Notification result = null;
			lock (connLock)
			{
				SenderThread senderThread = getSenderThread(wakeNew: true);
				if (st == senderThread)
				{
					lock (publishLock)
					{
						if (publishQueue.Count != 0)
						{
							return publishQueue[0];
						}
						return result;
					}
				}
				return result;
			}
		}

		protected internal virtual void removeFirstPublished(Notification re, SenderThread st)
		{
			lock (connLock)
			{
				SenderThread senderThread = getSenderThread(wakeNew: true);
				if (st != senderThread)
				{
					return;
				}
				lock (publishLock)
				{
					if (publishQueue.Count != 0)
					{
						Notification notification = publishQueue[0];
						publishQueue.RemoveAt(0);
						if (notification != re)
						{
							publishQueue.Insert(0, notification);
						}
					}
				}
			}
		}

		protected internal virtual void clearPublishedSender(SenderThread st)
		{
			lock (connLock)
			{
				if (st == senderThread)
				{
					senderThread = null;
					getSenderThread(wakeNew: true);
				}
			}
		}

		protected internal virtual void send(SubscriptionNotification se)
		{
			lock (connLock)
			{
				if (shutdown)
				{
					return;
				}
				for (int i = 0; i < connections.Length; i++)
				{
					if (connections[i].sender != null)
					{
						connections[i].sender.send(se);
					}
				}
			}
		}

		private void buildConnectionArray(string hplist)
		{
			string[] array = (string[])((!ons.useSCAN) ? ((object)hplist.Split(',')) : ((object)scanNodes(hplist)));
			int num = 0;
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i] == null)
				{
					continue;
				}
				for (int j = 0; j < array.Length; j++)
				{
					if (j != i && array[j] != null && array[i].Equals(array[j]))
					{
						array[j] = null;
						break;
					}
				}
				int num2 = array[i].LastIndexOf(':');
				if (num2 != -1)
				{
					int num3;
					try
					{
						num3 = int.Parse(array[i].Substring(num2 + 1));
						if (num3 > 0)
						{
							num++;
						}
					}
					catch (Exception ex)
					{
						if (ProviderConfig.m_bTraceLevelPrivate)
						{
							Trace.Write(OracleTraceLevel.Private, OracleTraceTag.Error, OracleTraceClassName.NodeList, OracleTraceFuncName.buildConnectionArray, "NodeList::buildConnectionArray() failed. -" + ex.Message);
						}
						num3 = 0;
					}
					if (num3 <= 0)
					{
						array[i] = null;
					}
				}
				else
				{
					array[i] = null;
				}
			}
			if (num != 0)
			{
				connections = new Connection[num];
				int num4 = 0;
				for (int i = 0; i < array.Length; i++)
				{
					if (array[i] == null)
					{
						continue;
					}
					int num2 = array[i].LastIndexOf(':');
					if (num2 == -1)
					{
						continue;
					}
					try
					{
						int num3 = int.Parse(array[i].Substring(num2 + 1));
						connections[num4] = new Connection(this, array[i].Substring(0, num2), num3, i);
						num4++;
					}
					catch (Exception ex2)
					{
						if (ProviderConfig.m_bTraceLevelPrivate)
						{
							Trace.Write(OracleTraceLevel.Private, OracleTraceTag.Error, OracleTraceClassName.NodeList, OracleTraceFuncName.buildConnectionArray, "NodeList::buildConnectionArray() failed. -" + ex2.Message);
						}
					}
				}
				if (num4 != num)
				{
					connections = null;
					throw new ONSException(OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.ONS_HOST_PORT_PARSE_ERROR, id, hplist));
				}
				return;
			}
			throw new ONSException(OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.ONS_NO_VALID_HOST_PORT_VALUES, id, hplist));
		}

		private string[] scanNodes(string hpList)
		{
			StringBuilder stringBuilder = new StringBuilder();
			string[] array = hpList.Split(',');
			foreach (string text in array)
			{
				string[] array2 = text.Split(':');
				if (array2.Length != 2)
				{
					throw new ONSException(OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.ONS_BAD_FORMAT_OF_NODES, id, text));
				}
				string hostNameOrAddress = array2[0];
				string str = array2[1];
				IPAddress[] array3 = null;
				try
				{
					array3 = Dns.GetHostAddresses(hostNameOrAddress);
				}
				catch (Exception)
				{
					throw new ONSException(OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.ONS_WRONG_SERVER_NODES_CONFIG, id, hpList));
				}
				if (array3.Length == 1)
				{
					if (stringBuilder.Length != 0)
					{
						stringBuilder.Append(',');
					}
					stringBuilder.Append(text);
					continue;
				}
				IPAddress[] array4 = array3;
				foreach (IPAddress obj in array4)
				{
					if (stringBuilder.Length != 0)
					{
						stringBuilder.Append(',');
					}
					string value = obj.ToString() + ":" + str;
					stringBuilder.Append(value);
				}
			}
			string[] array5 = stringBuilder.ToString().Split(',');
			if (ons.defaultList)
			{
				nodesRandomize(array5);
			}
			return array5;
		}

		private void nodesRandomize(string[] nodes)
		{
			int num = nodes.Length;
			if (num > 1)
			{
				Random random = new Random();
				for (int i = 0; i < num; i++)
				{
					int num2 = random.Next(num);
					string text = nodes[i];
					nodes[i] = nodes[num2];
					nodes[num2] = text;
				}
			}
		}

		protected internal virtual void checkConnections(Connection conn)
		{
			joinStaleRecievers(conn.receiver);
			long num = 0L;
			setConnectionsBusy();
			int concurrencyIndex = conn.ConcurrencyIndex;
			if (conn.receiver != null && !conn.shutdown && !shutdown)
			{
				int num2 = concurrencies[concurrencyIndex].assignedIndex + 1;
				for (int i = 0; i < connections.Length; i++)
				{
					if (num2 == connections.Length)
					{
						num2 = 0;
					}
					if (num2 == concurrencies[concurrencyIndex].scanIndex)
					{
						long num3 = (DateTime.Now.Ticks - 621355968000000000L) / 10000;
						if (!failedOver_Renamed_Field)
						{
							concurrencies[concurrencyIndex].setListFailed();
							if (active)
							{
								bool flag = true;
								for (int j = 0; j < concurrency; j++)
								{
									if (!concurrencies[j].listFailed)
									{
										flag = false;
										break;
									}
								}
								if (flag && ons.nodeListFailOver(this, concurrencyIndex))
								{
									break;
								}
							}
						}
						long num4 = num3 - concurrencies[concurrencyIndex].scanTime;
						if (num4 < 30000)
						{
							num = 30000 - num4;
						}
						if (num < 5000)
						{
							num = 5000L;
						}
						concurrencies[concurrencyIndex].ScanTime = num3;
					}
					else if (concurrencies[concurrencyIndex].listFailed)
					{
						num = 5000L;
					}
					if (connections[num2] != conn && connections[num2].receiver == null)
					{
						connections[num2].ScanDelay = num;
						replaceConnection(conn, connections[num2], concurrencyIndex);
						num = 0L;
						break;
					}
					num2++;
				}
				if (num != 0L)
				{
					conn.ScanDelay = num;
				}
			}
			else if (conn.receiver != null)
			{
				replaceConnection(conn, null, concurrencyIndex);
			}
			clearConnectionsBusy();
		}

		private void replaceConnection(Connection oConn, Connection nConn, int index)
		{
			stopConnection(oConn);
			joinConnection(oConn, shutdownState: false);
			if (nConn != null)
			{
				concurrencies[index].assign(nConn);
				startConnection(nConn);
			}
			else
			{
				concurrencies[index].clear();
			}
		}

		private void stopConnection(Connection conn)
		{
			conn.ClientShutdown = true;
			conn.receiver.shutdown();
			conn.sender.shutdown();
		}

		private void joinConnection(Connection conn, bool shutdownState)
		{
			lock (staleReceivers)
			{
				staleReceivers.Add(conn.receiver);
			}
			bool flag;
			do
			{
				try
				{
					conn.sender.Join();
					flag = true;
				}
				catch (Exception ex)
				{
					if (ProviderConfig.m_bTraceLevelPrivate)
					{
						Trace.Write(OracleTraceLevel.Private, OracleTraceTag.Error, OracleTraceClassName.NodeList, OracleTraceFuncName.joinConnection, "NodeList::joinConnection() failed. -" + ex.Message);
					}
					flag = false;
				}
			}
			while (!flag);
			conn.ClientSender = null;
			conn.ClientReceiver = null;
			conn.ClientShutdown = shutdownState;
		}

		protected internal virtual void establishedConnection(Connection conn)
		{
			setConnectionsBusy();
			int concurrencyIndex = conn.ConcurrencyIndex;
			concurrencies[concurrencyIndex].connected();
			if (failedOver_Renamed_Field)
			{
				ons.nodeListFallBack(this, concurrencyIndex);
			}
			clearConnectionsBusy();
			joinStaleRecievers(conn.receiver);
		}

		protected internal virtual void failedOver(NodeList failOver, int cIndex)
		{
			failedOver_Renamed_Field = true;
			failOverList = failOver;
			for (int i = 0; i < concurrency; i++)
			{
				if (i != cIndex)
				{
					int assignedIndex = concurrencies[i].assignedIndex;
					if (connections[assignedIndex].receiver != null)
					{
						replaceConnection(connections[assignedIndex], null, i);
					}
				}
			}
		}

		protected internal virtual void fallBack(int cIndex)
		{
			failedOver_Renamed_Field = false;
			failOverList = null;
			int i = 0;
			for (int j = 0; j < concurrency; j++)
			{
				if (j == cIndex)
				{
					continue;
				}
				for (; i < connections.Length; i++)
				{
					if (connections[i].receiver == null)
					{
						concurrencies[j].assign(connections[i]);
						startConnection(connections[i]);
						break;
					}
				}
			}
		}

		private void joinStaleRecievers(ReceiverThread caller)
		{
			ReceiverThread receiverThread = null;
			lock (staleReceivers)
			{
				try
				{
					if (staleReceivers.Count > 0)
					{
						receiverThread = staleReceivers[0];
						staleReceivers.RemoveAt(0);
					}
					while (receiverThread != null)
					{
						if (receiverThread != caller)
						{
							bool flag;
							do
							{
								try
								{
									receiverThread.Join();
									flag = true;
								}
								catch (ThreadInterruptedException ex)
								{
									if (ProviderConfig.m_bTraceLevelPrivate)
									{
										Trace.Write(OracleTraceLevel.Private, OracleTraceTag.Error, OracleTraceClassName.NodeList, OracleTraceFuncName.joinStaleRecievers, "NodeList::joinStaleReceivers() failed. -" + ex.Message);
									}
									flag = false;
								}
							}
							while (!flag);
						}
						receiverThread = staleReceivers[0];
						staleReceivers.RemoveAt(0);
					}
				}
				catch (ArgumentOutOfRangeException ex2)
				{
					if (ProviderConfig.m_bTraceLevelPrivate)
					{
						Trace.Write(OracleTraceLevel.Private, OracleTraceTag.Error, OracleTraceClassName.NodeList, OracleTraceFuncName.joinStaleRecievers, "NodeList::joinStaleReceivers() failed. -" + ex2.Message);
					}
				}
			}
		}

		protected internal virtual void stop()
		{
			setConnectionsBusy();
			for (int i = 0; i < concurrency; i++)
			{
				int assignedIndex = concurrencies[i].assignedIndex;
				if (assignedIndex != -1)
				{
					stopConnection(connections[assignedIndex]);
				}
			}
			clearConnectionsBusy();
		}

		protected internal virtual void join()
		{
			setConnectionsBusy();
			for (int i = 0; i < concurrency; i++)
			{
				int assignedIndex = concurrencies[i].assignedIndex;
				if (assignedIndex != -1)
				{
					joinConnection(connections[assignedIndex], shutdownState: true);
				}
				concurrencies[i].clear();
			}
			clearConnectionsBusy();
			joinStaleRecievers(null);
			lock (publishLock)
			{
				while (publishQueue.Count != 0)
				{
					publishQueue.RemoveAt(0);
				}
			}
			lock (notifications)
			{
				notifications.Clear();
			}
		}

		private void setConnectionsBusy()
		{
			bool flag = true;
			do
			{
				lock (connLock)
				{
					if (!connCheck)
					{
						connCheck = true;
						flag = false;
					}
					if (!flag)
					{
						continue;
					}
					try
					{
						Monitor.Wait(connLock);
					}
					catch (Exception ex)
					{
						if (ProviderConfig.m_bTraceLevelPrivate)
						{
							Trace.Write(OracleTraceLevel.Private, OracleTraceTag.Error, OracleTraceClassName.NodeList, OracleTraceFuncName.setConnectionsBusy, "NodeList::setConnectionBusy() failed. -" + ex.Message);
						}
					}
				}
			}
			while (flag);
		}

		private void clearConnectionsBusy()
		{
			lock (connLock)
			{
				connCheck = false;
				Monitor.Pulse(connLock);
			}
		}

		protected internal virtual void deliver(Notification e)
		{
			if (ons.localConn || !isDupNotification(e))
			{
				ons.deliver(e);
			}
		}

		private bool isDupNotification(Notification e)
		{
			if (e.id_Renamed_Field == null || e.instanceName_Renamed_Field == null)
			{
				return false;
			}
			long num = (DateTime.Now.Ticks - 621355968000000000L) / 10000;
			lock (notifications)
			{
				if (num - lastCleanupTime >= ons.notificationtimeout)
				{
					lastCleanupTime = num;
					cleanupNotificationTable(num);
				}
				string key = e.instanceName_Renamed_Field + e.id_Renamed_Field;
				NotificationInformation notificationInformation = (NotificationInformation)notifications[key];
				if (notificationInformation == null)
				{
					notificationInformation = new NotificationInformation(num);
					notificationInformation.addCount();
					notifications[key] = notificationInformation;
					return false;
				}
				notificationInformation.addCount();
				cleanupNotificationTable(key, notificationInformation);
			}
			return true;
		}

		private void cleanupNotificationTable(string key, NotificationInformation elem)
		{
			if (elem.Count >= concurrency)
			{
				notifications.Remove(key);
			}
		}

		private void cleanupNotificationTable(long currentTime)
		{
			List<string> list = new List<string>();
			lock (notifications)
			{
				IEnumerator enumerator = notifications.GetEnumerator();
				while (enumerator.MoveNext())
				{
					DictionaryEntry dictionaryEntry = (DictionaryEntry)enumerator.Current;
					NotificationInformation notificationInformation = (NotificationInformation)dictionaryEntry.Value;
					if (currentTime - notificationInformation.Timestamp > ons.notificationtimeout)
					{
						list.Add((string)dictionaryEntry.Key);
					}
				}
				foreach (string item in list)
				{
					notifications.Remove(item);
				}
			}
		}
	}
}
